package com.cst.security.authentication.myException;

import com.cst.security.authentication.config.CstResourceServerConfig;
import com.cst.security.baseUtils.properties.OAuth2ClientProperties;
import com.cst.security.baseUtils.properties.SecurityProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.codec.Base64;
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestOperations;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * 认证异常处理（身份认证异常）
 */
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {

    private final static Logger logger = LoggerFactory.getLogger(MyAuthenticationEntryPoint.class);

    @Autowired
    private SecurityProperties securityProperties;

    private RestOperations restTemplate = new RestTemplate();

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws ServletException {

        Map<String, Object> returnMap = new HashMap<String, Object>();
        Throwable cause = authException.getCause();
        // if token过期异常(if重刷token成功(返回新token到前台)，失败(返回无效token异常))，else未授权异常(返回未认证异常)
        if(cause instanceof InvalidTokenException) {
            //尝试刷新token，如果是token超时则更新新token
            try{
                returnMap = refreshToken(request, response);
                response.setStatus(HttpServletResponse.SC_CONFLICT);
                //409 Conflict 由于和被请求的资源的当前状态之间存在冲突，请求无法完成。这个代码只允许用在这样的情况下才能被使用：用户被认为能够解决冲突，并且会重新提交新的请求。该响应应当包含足够的信息以便用户发现冲突的源头。
            }catch (Exception e){
                //抛出异常 表示无法刷新 原token是无效token，或者refresh_token也过了有效期
                returnMap.put("code", 401);//401
                returnMap.put("msg", "无效的token");
                e.printStackTrace();
                returnMap.put("data", authException.getMessage());
                returnMap.put("success", false);
                returnMap.put("path", request.getServletPath());
                returnMap.put("timestamp", String.valueOf(new Date().getTime()));
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            }
        }else{
            returnMap.put("code", HttpStatus.UNAUTHORIZED);//"Unauthorized"
            returnMap.put("msg", "登录后才可访问");
            returnMap.put("data", authException.getMessage());
            returnMap.put("success", false);
            returnMap.put("path", request.getServletPath());
            returnMap.put("timestamp", String.valueOf(new Date().getTime()));
            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        }
        response.setContentType("application/json");
        try {
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(response.getOutputStream(), returnMap);
        } catch (Exception e) {
            throw new ServletException();
        }
    }

    /**尝试刷新token**/
    private Map<String, Object> refreshToken(HttpServletRequest request, HttpServletResponse response) throws Exception{
        String client_id = securityProperties.getOauth2().getClientId();
        String client_secret = securityProperties.getOauth2().getClientSecret();
        String refreshToken = request.getParameter("refresh_token");
        MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
        formData.add("grant_type", "refresh_token");
        formData.add("refresh_token", refreshToken);
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", this.getAuthorizationHeader(client_id, client_secret));
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        String url = "http://localhost:8888/oauth/token";
        Map<String, Object> map = this.postForMap(url, formData, headers);
        if (map.containsKey("error")) {
            // refresh_token 刷新失败
            throw new Exception();
        } else {
            logger.info(map.toString());
        }
        return map;
    }

    private String getAuthorizationHeader(String clientId, String clientSecret) {
        String creds = String.format("%s:%s", clientId, clientSecret);
        try {
            return "Basic " + new String(Base64.encode(creds.getBytes("UTF-8")));
        } catch (UnsupportedEncodingException var5) {
            throw new IllegalStateException("Could not convert String");
        }
    }

    private Map<String, Object> postForMap(String path, MultiValueMap<String, String> formData, HttpHeaders headers) {
        Map map = (Map)this.restTemplate.exchange(path, HttpMethod.POST, new HttpEntity(formData, headers), Map.class, new Object[0]).getBody();
        return map;
    }
}
